/*
 * Copyright (c) 2023 Cshoo Org. All Rights Reserved.
 */

package org.tattoo.workflow.domain.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Task;
import org.cshoo.tattoo.embed.domain.feign.dto.QuerySingleLookupResp;
import org.cshoo.tattoo.embed.domain.service.DataCenterService;
import org.cshoo.tattoo.embed.domain.service.TemplateSdkService;
import org.cshoo.tattoo.embed.infra.exception.ValidateException;
import org.cshoo.tattoo.embed.infra.utils.UserUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.tattoo.workflow.domain.bo.Approve;
import org.tattoo.workflow.domain.bo.ProcurementOrderBo;
import org.tattoo.workflow.domain.bo.Review;
import org.tattoo.workflow.domain.bo.Submit;
import org.tattoo.workflow.domain.consts.ProcurementProcessOperate;
import org.tattoo.workflow.domain.consts.QueryProcurementMode;
import org.tattoo.workflow.domain.po.ProcessRecord;
import org.tattoo.workflow.domain.po.ProcurementItem;
import org.tattoo.workflow.domain.po.ProcurementOrder;
import org.tattoo.workflow.domain.repository.IProcessRecordRepository;
import org.tattoo.workflow.domain.repository.IProcurementItemRepository;
import org.tattoo.workflow.domain.repository.IProcurementOrderRepository;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 杨翼
 * @since 1.0
 */
@Service
@Slf4j
public class ProcurementService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private TaskService taskService;

    @Resource
    private DataCenterService dataCenterService;

    @Resource
    private TemplateSdkService templateSdkService;

    @Resource
    private IProcurementOrderRepository procurementOrderRepository;

    @Resource
    private IProcurementItemRepository procurementItemRepository;

    @Resource
    private IProcessRecordRepository processRecordRepository;

    /**
     * 根据id查询订单信息
     *
     * @param id id
     * @return 订单信息
     */
    public ProcurementOrderBo findProcurementOrder(Long id) {
        ProcurementOrderBo result = new ProcurementOrderBo();
        Optional<ProcurementOrder> procurementOrderOptional = procurementOrderRepository.findById(id);
        if (procurementOrderOptional.isPresent()) {
            ProcurementOrder procurementOrder = procurementOrderOptional.orElse(null);
            BeanUtils.copyProperties(procurementOrder, result);
            String processInstanceId = procurementOrder.getProcessInstanceId();
            if (StringUtils.isNotBlank(processInstanceId)) {
                Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
                log.info("task:{}", task);
                if (Objects.nonNull(task) && StringUtils.isNotBlank(task.getAssignee())) {
                    result.setCurrentHandler(task.getAssignee());
                }
            } else {
                result.setCurrentHandler(procurementOrder.getCreatedBy());
            }
        }
        return result;
    }

    /**
     * 查询采购订单明细
     *
     * @param procurementOrderId 采购订单id
     * @return 采购订单明细
     */
    public List<ProcurementItem> queryProcurementItems(Long procurementOrderId) {
        return procurementItemRepository.findByProcurementOrderId(procurementOrderId);
    }

    /**
     * 保存采购订单
     * 当前端有id传入，为修改操作，否则为新增操作
     *
     * @param procurementOrder 采购订单
     */
    public Long saveProcurementOrder(ProcurementOrder procurementOrder) throws Exception {
        Long id = procurementOrder.getId();
        if (Objects.nonNull(id)) {
            procurementOrder.setLastModifiedBy(UserUtils.getUser().getUsername());
            procurementOrder.setLastModifiedDate(new Date());
            procurementOrderRepository.updateProcurementOrder(procurementOrder);
        } else {
            procurementOrder.setCreator(UserUtils.getUser().getUsername());
            procurementOrder.setCode(this.generateProcurementOrderCode());
            procurementOrder.setStatus(
                    dataCenterService.getLookupItemValue("procurement-order-status", "draft", Integer.class));
            procurementOrder.setStatus(0);
            procurementOrderRepository.save(procurementOrder);
            id = procurementOrder.getId();
        }
        return id;
    }

    /**
     * 保存采购订单明细，单条保存
     * 当前端有id传入，为修改操作，否则为新增操作
     *
     * @param procurementItem 采购明细
     */
    public void saveProcurementItem(ProcurementItem procurementItem) throws Exception {
        Long id = procurementItem.getId();
        if (Objects.nonNull(id)) {
            procurementItem.setLastModifiedBy(UserUtils.getUser().getUsername());
            procurementItem.setLastModifiedDate(new Date());
            procurementItemRepository.updateProcurementItem(procurementItem);
        } else {
            if (Objects.isNull(procurementItem.getProcurementOrderId())) {
                throw new ValidateException("procurementOrderId is required");
            }
            procurementItemRepository.save(procurementItem);
        }
    }

    /**
     * 删除采购明细
     *
     * @param id 采购明细id
     */
    public void deleteProcurementItem(Long id) {
        procurementItemRepository.deleteById(id);
    }

    /**
     * 提交采购订单，提交时将产生对应的工作流
     *
     * @param submit 提交对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void submitProcurementOrder(Submit submit) throws Exception {
        ProcurementOrder procurementOrder = procurementOrderRepository.findById(submit.getId()).orElse(null);
        if (Objects.nonNull(procurementOrder)) {
            String processInstanceId = procurementOrder.getProcessInstanceId();
            if (StringUtils.isBlank(processInstanceId)) {
                ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Process_procurement");
                processInstanceId = processInstance.getProcessInstanceId();
            }
            log.info("processInstanceId:{}", processInstanceId);
            procurementOrder.setProcessInstanceId(processInstanceId);
            procurementOrder.setStatus(
                    dataCenterService.getLookupItemValue("procurement-order-status", "review", Integer.class));
            procurementOrder.setSubmitter(UserUtils.getUser().getUsername());
            this.saveProcurementOrder(procurementOrder);
            Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            taskService.complete(task.getId());

            Task reviewTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            reviewTask.setAssignee(submit.getReviewer());
            taskService.saveTask(reviewTask);

            ProcessRecord processRecord = new ProcessRecord();
            processRecord.setProcurementOrderId(procurementOrder.getId());
            processRecord.setStatus(
                    dataCenterService.getLookupItemValue("procurement-order-status", "draft", Integer.class));
            processRecord.setOperate(ProcurementProcessOperate.ACCEPT.getValue());
            processRecord.setHandler(UserUtils.getUser().getUsername());
            processRecordRepository.save(processRecord);
        }
    }

    /**
     * 撤回采购订单
     *
     * @param id 采购订单id
     * @throws Exception 异常
     */
    public void withdrawProcurementOrder(Long id) throws Exception {
        ProcurementOrder procurementOrder = procurementOrderRepository.findById(id).orElse(null);
        if (Objects.nonNull(procurementOrder)) {
            String processInstanceId = procurementOrder.getProcessInstanceId();
            Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            String taskId = task.getId();
            Map<String, Object> variables = new LinkedHashMap<>();
            variables.put("operate", ProcurementProcessOperate.WITHDRAW.getValue());

            taskService.complete(taskId, variables);

            Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            nextTask.setAssignee(procurementOrder.getSubmitter());
            taskService.saveTask(nextTask);

            procurementOrder.setStatus(dataCenterService.getLookupItemValue("procurement-order-status", "draft", Integer.class));
            this.saveProcurementOrder(procurementOrder);

            ProcessRecord processRecord = new ProcessRecord();
            processRecord.setProcurementOrderId(procurementOrder.getId());
            processRecord.setStatus(dataCenterService.getLookupItemValue("procurement-order-status", "review", Integer.class));
            processRecord.setOperate(ProcurementProcessOperate.WITHDRAW.getValue());
            processRecord.setHandler(UserUtils.getUser().getUsername());
            processRecordRepository.save(processRecord);
        }
    }

    /**
     * 复合采购订单
     *
     * @param review 复合对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void reviewProcurementOrder(Review review) throws Exception {
        ProcurementOrder procurementOrder = procurementOrderRepository.findById(review.getId()).orElse(null);
        if (Objects.nonNull(procurementOrder)) {
            String processInstanceId = procurementOrder.getProcessInstanceId();
            Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            String taskId = task.getId();
            if (StringUtils.isNotBlank(review.getComment())) {
                taskService.createComment(taskId, processInstanceId, review.getComment());
            }
            Map<String, Object> variables = new LinkedHashMap<>();
            variables.put("operate", review.getOperate());
            taskService.setAssignee(taskId, review.getApprover());
            taskService.complete(taskId, variables);

            Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            if (Objects.equals(review.getOperate(), ProcurementProcessOperate.ACCEPT.getValue())) {
                nextTask.setAssignee(review.getApprover());
                procurementOrder.setStatus(
                        dataCenterService.getLookupItemValue("procurement-order-status", "approve", Integer.class));
            } else {
                nextTask.setAssignee(procurementOrder.getSubmitter());
                procurementOrder.setStatus(
                        dataCenterService.getLookupItemValue("procurement-order-status", "draft", Integer.class));
            }

            taskService.saveTask(nextTask);

            procurementOrder.setReviewer(UserUtils.getUser().getUsername());
            this.saveProcurementOrder(procurementOrder);

            ProcessRecord processRecord = new ProcessRecord();
            processRecord.setProcurementOrderId(procurementOrder.getId());
            processRecord.setStatus(
                    dataCenterService.getLookupItemValue("procurement-order-status", "review", Integer.class));
            processRecord.setOperate(review.getOperate());
            processRecord.setHandler(UserUtils.getUser().getUsername());
            processRecord.setContent(review.getComment());
            processRecordRepository.save(processRecord);
        }
    }

    /**
     * 审批采购订单
     *
     * @param approve 审批对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void approveProcurementOrder(Approve approve) throws Exception {
        ProcurementOrder procurementOrder = procurementOrderRepository.findById(approve.getId()).orElse(null);
        if (Objects.nonNull(procurementOrder)) {
            String processInstanceId = procurementOrder.getProcessInstanceId();
            Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            String taskId = task.getId();
            taskService.createComment(taskId, processInstanceId, approve.getComment());
            Map<String, Object> variables = new LinkedHashMap<>();
            variables.put("operate", approve.getOperate());
            taskService.complete(taskId, variables);

            if (Objects.equals(approve.getOperate(), ProcurementProcessOperate.ACCEPT.getValue())) {
                procurementOrder.setStatus(
                        dataCenterService.getLookupItemValue("procurement-order-status", "complete", Integer.class));
            } else {
                Task applyTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
                applyTask.setAssignee(procurementOrder.getSubmitter());
                procurementOrder.setStatus(
                        dataCenterService.getLookupItemValue("procurement-order-status", "draft", Integer.class));
                taskService.saveTask(applyTask);
            }

            procurementOrder.setApprover(UserUtils.getUser().getUsername());
            this.saveProcurementOrder(procurementOrder);

            ProcessRecord processRecord = new ProcessRecord();
            processRecord.setProcurementOrderId(procurementOrder.getId());
            processRecord.setStatus(
                    dataCenterService.getLookupItemValue("procurement-order-status", "approve", Integer.class));
            processRecord.setOperate(approve.getOperate());
            processRecord.setHandler(UserUtils.getUser().getUsername());
            processRecord.setContent(approve.getComment());
            processRecordRepository.save(processRecord);
        }
    }

    /**
     * 查询流程记录
     *
     * @param procurementOrderId 采购订单id
     * @return 流程记录
     */
    public List<ProcessRecord> queryProcessRecords(Long procurementOrderId) {
        return processRecordRepository.queryProcessRecordByProcurementOrderIdOrderByCreatedDate(procurementOrderId);
    }

    /**
     * 查询采购订单
     *
     * @param queryMode 查询方式
     * @return 查询采购订单
     */
    public List<ProcurementOrderBo> queryProcurementOrders(QueryProcurementMode queryMode) throws Exception {
        List<ProcurementOrderBo> result = Lists.newArrayList();

        switch (queryMode) {
            case TODOS:
                Integer completeStatus = dataCenterService.getLookupItemValue("procurement-order-status", "complete",
                        Integer.class);
                List<ProcurementOrder> procurementOrders = procurementOrderRepository
                        .queryByStatusNotOrderByLastModifiedDateDesc(completeStatus);
                List<Task> tasks = taskService.createTaskQuery().processInstanceIdIn(procurementOrders.stream()
                        .map(ProcurementOrder::getProcessInstanceId).distinct().toArray(String[]::new)).list();
                List<Task> currentUserTasks = tasks.stream()
                        .filter(task -> StringUtils.equals(task.getAssignee(), UserUtils.getUser().getUsername()))
                        .collect(Collectors.toList());
                List<String> currentUserProcessIds = currentUserTasks.stream().map(Task::getProcessInstanceId)
                        .collect(Collectors.toList());
                List<ProcurementOrder> todoProcurementOrders = procurementOrders.stream()
                        .filter(po -> currentUserProcessIds.contains(po.getProcessInstanceId()))
                        .collect(Collectors.toList());
                result = this.buildProcurementOrderBos(todoProcurementOrders, currentUserTasks);
                break;
            case RELATED:
                List<ProcurementOrder> relatedProcurementOrders = procurementOrderRepository
                        .queryProcurementOrders(UserUtils.getUser().getUsername());
                List<Task> relatedTasks = taskService.createTaskQuery().processInstanceIdIn(relatedProcurementOrders
                        .stream().map(ProcurementOrder::getProcessInstanceId).distinct().toArray(String[]::new)).list();
                result = this.buildProcurementOrderBos(relatedProcurementOrders, relatedTasks);
                break;
        }

        return result;
    }

    /**
     * 导出采购订单
     *
     * @return 采购订单excel数据流
     */
    public OutputStream exportProcurementOrders() throws Exception {
        List<ProcurementOrderBo> todoProcurementOrderBos = this.queryProcurementOrders(QueryProcurementMode.TODOS);
        List<Map<String, Object>> todoProcurementOrderBoMaps = this.buildExportProcurementOrderData(todoProcurementOrderBos);

        List<ProcurementOrderBo> relatedProcurementOrderBos = this.queryProcurementOrders(QueryProcurementMode.RELATED);
        List<Map<String, Object>> relatedProcurementOrderBoMaps = this.buildExportProcurementOrderData(relatedProcurementOrderBos);

        InputStream templateIn = templateSdkService.getFileTemplate("procurement-orders");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (ExcelWriter build = EasyExcel.write(out).withTemplate(templateIn).build()) {
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            WriteSheet todoSheet = EasyExcel.writerSheet("TODO").build();
            build.fill(todoProcurementOrderBoMaps, fillConfig, todoSheet);
            WriteSheet relatedSheet = EasyExcel.writerSheet("Related").build();
            build.fill(relatedProcurementOrderBoMaps, fillConfig, relatedSheet);
            build.finish();
        }

        return out;
    }

    /**
     * 构造导出采购订单数据
     *
     * @param procurementOrders 采购订单bo列表
     * @return 导出采购订单数据
     */
    private List<Map<String, Object>> buildExportProcurementOrderData(List<ProcurementOrderBo> procurementOrders) throws Exception {
        List<Map<String, Object>> result = Lists.newArrayList();

        QuerySingleLookupResp querySingleLookupResp = dataCenterService.querySingleLookup("procurement-order-status");
        Map<Integer, String> procurementOrderStatusMap = Maps.newHashMap();
        querySingleLookupResp.getLookupItems().forEach(
                item -> procurementOrderStatusMap.put(Integer.parseInt(item.getValue()), item.getCode())
        );

        procurementOrders.forEach(po -> {
            Map<String, Object> map = Maps.newLinkedHashMap();
            map.put("id", po.getId());
            map.put("code", po.getCode());
            map.put("name", po.getName());
            map.put("description", po.getDescription());
            map.put("currentHandler", po.getCurrentHandler());
            map.put("creator", po.getCreator());
            map.put("submitter", po.getSubmitter());
            map.put("reviewer", po.getReviewer());
            map.put("approver", po.getApprover());
            map.put("status", procurementOrderStatusMap.get(po.getStatus()));
            result.add(map);
        });
        return result;
    }

    /**
     * 构造采购订单bo集合
     * 
     * @param procurementOrders 采购订单po集合
     * @param tasks             工作流任务集合
     * @return 采购订单bo集合
     */
    private List<ProcurementOrderBo> buildProcurementOrderBos(List<ProcurementOrder> procurementOrders,
            List<Task> tasks) {
        List<ProcurementOrderBo> result = Lists.newArrayList();
        procurementOrders.forEach(
                po -> {
                    ProcurementOrderBo procurementOrderBo = new ProcurementOrderBo();
                    BeanUtils.copyProperties(po, procurementOrderBo);
                    tasks.stream()
                            .filter(ts -> StringUtils.equals(ts.getProcessInstanceId(), po.getProcessInstanceId()))
                            .findAny().ifPresent(
                                    ts -> procurementOrderBo.setCurrentHandler(ts.getAssignee()));
                    result.add(procurementOrderBo);
                });
        return result;
    }

    /**
     * 生成采购订单号
     *
     * @return 采购订单号
     */
    private String generateProcurementOrderCode() {
        String header = "PO";
        String dateStr = DateFormatUtils.format(new Date(), "yyyyMMdd");
        String key = header + "-" + dateStr + "-";
        int step = 1;
        Long increment = redisTemplate.opsForValue().increment(key, step);
        if (Objects.nonNull(increment) && increment.intValue() == step) {
            redisTemplate.expire(key, 25, TimeUnit.HOURS);
        }
        return key + String.format("%03d", increment);
    }
}
